/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.StaticTextSqlNode;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;
import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.jianggujin.dbfly.mybatis.xmltag.JAliasHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JBindHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JChooseHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JConditionHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JCustomNodeHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JFnHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JForEachHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JIfHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JNodeHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JOtherwiseHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JPageHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JSetHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JTrimHandler;
import com.jianggujin.dbfly.mybatis.xmltag.JWhereHandler;
import com.jianggujin.dbfly.util.JStringUtils;

public class JXMLScriptBuilder extends BaseBuilder {

    private final XNode context;
    private boolean isDynamic;
    private final Class<?> parameterType;
    private static final Map<String, JNodeHandler> NODE_HANDLER_MAP = new HashMap<String, JNodeHandler>();

    static {
        NODE_HANDLER_MAP.put("trim", new JTrimHandler());
        NODE_HANDLER_MAP.put("where", new JWhereHandler());
        NODE_HANDLER_MAP.put("set", new JSetHandler());
        NODE_HANDLER_MAP.put("foreach", new JForEachHandler());
        NODE_HANDLER_MAP.put("if", new JIfHandler());
        NODE_HANDLER_MAP.put("choose", new JChooseHandler());
        NODE_HANDLER_MAP.put("when", new JIfHandler());
        NODE_HANDLER_MAP.put("otherwise", new JOtherwiseHandler());
        NODE_HANDLER_MAP.put("bind", new JBindHandler());
        NODE_HANDLER_MAP.put("fn", new JFnHandler());
        NODE_HANDLER_MAP.put("page", new JPageHandler());
        NODE_HANDLER_MAP.put("alias", new JAliasHandler());
        NODE_HANDLER_MAP.put("condition", new JConditionHandler());
        Iterator<JNodeHandler> iterator = ServiceLoader.load(JNodeHandler.class).iterator();
        while (iterator.hasNext()) {
            setNodeHandler(iterator.next());
        }
    }

    public JXMLScriptBuilder(Configuration configuration, XNode context) {
        this(configuration, context, null);
    }

    public JXMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
        super(configuration);
        this.context = context;
        this.parameterType = parameterType;
    }

    public SqlSource parseScriptNode() {
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource = null;
        if (isDynamic) {
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }

    public MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            XNode child = node.newXNode(children.item(i));
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
                    || child.getNode().getNodeType() == Node.TEXT_NODE) {
                String data = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(data);
                if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    isDynamic = true;
                } else {
                    contents.add(new StaticTextSqlNode(data));
                }
            } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
                String nodeName = child.getNode().getNodeName();
                JNodeHandler handler = this.getNodeHandler(nodeName);
                if (handler == null) {
                    throw new BuilderException(JStringUtils.format("Unknown element <{}> in SQL statement.", nodeName));
                }
                handler.handleNode(this, child, contents);
                isDynamic = true;
            }
        }
        return new MixedSqlNode(contents);
    }

    /**
     * 获得节点处理器，函数节点处理器需要特殊处理
     * 
     * @param nodeName
     * @return
     */
    public JNodeHandler getNodeHandler(String nodeName) {
        if (nodeName == null) {
            return null;
        }
        if (nodeName.startsWith("fn_")) {
            nodeName = "fn";
        }
        return NODE_HANDLER_MAP.get(nodeName);
    }

    protected static Map<String, JNodeHandler> getNodeHandlerMap() {
        return NODE_HANDLER_MAP;
    }

    /**
     * 设置节点解析器
     * 
     * @param nodeHandler
     */
    public static void setNodeHandler(JNodeHandler nodeHandler) {
        if (nodeHandler instanceof JCustomNodeHandler) {
            JCustomNodeHandler customNodeHandler = (JCustomNodeHandler) nodeHandler;
            String[] nodeNames = customNodeHandler.nodeNames();
            if (nodeNames != null) {
                for (String nodeName : nodeNames) {
                    NODE_HANDLER_MAP.put(nodeName, nodeHandler);
                }
            }
        } else {
            String name = nodeHandler.getClass().getSimpleName();
            if (name.endsWith("Handler")) {
                name = name.substring(0, name.length() - 7);
            }
            // 默认类名首字母小写
            NODE_HANDLER_MAP.put(JStringUtils.firstCharToLowerCase(name), nodeHandler);
        }
    }
}
